#include "util/util.h"
#include <GL/glu.h>
#include <stdlib.h>	// rand()
#include <time.h>	// time(NULL)
using namespace util;
const int SCR_W = 640;
const int SCR_H = 480;
const int GRID = 64;      // grid density
struct vertex
{
	vec3 pos;
	vec3 nor;
	vec2 tex;
};
vertex* pvts = NULL;
short* pidxs = NULL;

struct light
{
	vec3 pos;
	vec3 vel;
	vec3 col;
};
const int LIGHTS_NUM = 64;
light lgts[LIGHTS_NUM];
const float WHITE[] = { 1.0f, 1.0f, 1.0f, 1.0f };
const float BLACK[] = { 0.0f, 0.0f, 0.0f, 1.0f };
const float GREY[] = { 0.1f, 0.1f, 0.1f, 1.0f };
CCamera cam;
CTexture tex;

void init()
{
	glViewport(0, 0, SCR_W, SCR_H);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_NORMALIZE);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glClearDepth(1.0);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);   // pass if depth equal
	glEnable(GL_CULL_FACE);
	
	int nvts = GRID * GRID;   // vertex number
	pvts = new vertex[nvts];

	for (int i=0; i<GRID; i++)
	{
		for (int j=0; j<GRID; j++)
		{
			// fill vertex data
			int idx = i * GRID + j;
			pvts[idx].pos.x = ((float)i / (GRID - 1)) * 2 - 1;
			pvts[idx].pos.y = 0.0f;
			pvts[idx].pos.z = ((float)j / (GRID - 1)) * 2 - 1;
			// fill normal data
			pvts[idx].nor = v3gen(0.0f, 1.0f, 0.0f);
			// fill texcoord data
			pvts[idx].tex.x = (float)i / (GRID - 1);
			pvts[idx].tex.y = -(float)j / (GRID - 1);
		}
	}
		
	pidxs = new short[2 * GRID * (GRID - 1)];
	for (int i=0; i<GRID-1; i++)
	{
		for (int j=0; j<GRID; j++)
		{
			pidxs[(i * GRID + j) * 2 + 0] = (i + 1) * GRID + j;
			pidxs[(i * GRID + j) * 2 + 1] = i * GRID + j;
		}
	}
	
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	
	glVertexPointer(3, GL_FLOAT, sizeof(vertex), &pvts[0].pos);
	glNormalPointer(GL_FLOAT, sizeof(vertex), &pvts[0].nor);
	glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), &pvts[0].tex);
	
	srand(time(NULL));
	// init all lights
	#define RANDF   ((float)rand() / RAND_MAX)
	for (int i=0; i<LIGHTS_NUM; i++)
	{
		lgts[i].pos = v3gen(RANDF * 2 - 1, 0.02f, RANDF * 2 - 1);
		lgts[i].vel = v3gen(RANDF * 2 - 1, 0.0f, RANDF * 2 - 1);
		lgts[i].col = v3gen(RANDF * 0.75f, RANDF * 0.75f, RANDF * 0.75f);
	}
	#undef RANDF
	
	tex.set_filter_high();
	tex.load_bmp("res/rock.bmp");
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, tex.get_id());
}
void frame()
{

}
void render()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	cam.perspective(45.0f, (float)SCR_W/SCR_H, 0.1f, 1000.f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	cam.lookat(0.0f, 1.8f, 1.8f,
			   0.0f, 0.0f, 0.0f,
			   0.0f, 1.0f, 0.0f);
	
	for (int i=0; i<LIGHTS_NUM; ++i)
	{
		lgts[i].pos.x += lgts[i].vel.x * 10.0f/1000;
		lgts[i].pos.z += lgts[i].vel.z * 10.0f/1000;

		// all lights bounce the borders
		if (lgts[i].pos.x > 1.0f && lgts[i].vel.x > 0.0f)
			lgts[i].vel.x = -lgts[i].vel.x;
		if (lgts[i].pos.x < -1.0f && lgts[i].vel.x < 0.0f)
			lgts[i].vel.x = -lgts[i].vel.x;

		if (lgts[i].pos.z > 1.0f && lgts[i].vel.z > 0.0f)
			lgts[i].vel.z = -lgts[i].vel.z;
		if (lgts[i].pos.z < -1.0f && lgts[i].vel.z < 0.0f)
			lgts[i].vel.z = -lgts[i].vel.z;
	}
	
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, GREY);	// global ambient
	static float adc[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, adc);
	
	glEnable(GL_LIGHTING);
	// draw scene 8 pass and blend results all together
	for (int i=0; i<8; i++)
	{
		for (int j=0; j<8; j++)
		{
			float lp[4] = { 0 };
			lp[0] = lgts[i * 8 + j].pos.x;
			lp[1] = lgts[i * 8 + j].pos.y;
			lp[2] = lgts[i * 8 + j].pos.z;
			lp[3] = 1.0f;
			glLightfv(GL_LIGHT0 + j, GL_POSITION, lp);
			
			float lc[4] = { 0 };
			lc[0] = lgts[i * 8 + j].col.x;
			lc[1] = lgts[i * 8 + j].col.y;
			lc[2] = lgts[i * 8 + j].col.z;
			lc[3] = 1.0f;
			glLightfv(GL_LIGHT0 + j, GL_DIFFUSE, lc);
			
			glEnable(GL_LIGHT0 + j);
		}
		
		// blend other pass times color
		// make material black so only the first time material work
		if (i != 0)
		{
			glEnable(GL_BLEND);
			glBlendFunc(GL_ONE, GL_ONE);
			
			glLightModelfv(GL_LIGHT_MODEL_AMBIENT, BLACK);	
		}
	
		for (int j=0; j<GRID-1; j++)
		//for (int j=0; j<GRID-1; j+=2)
			glDrawElements(GL_TRIANGLE_STRIP, GRID*2, GL_UNSIGNED_SHORT, &pidxs[j*GRID*2]);
	}
	
	glDisable(GL_LIGHTING);
	glDisable(GL_BLEND);
}

int main()
{
	g_p_system = new CSystem(SCR_W, SCR_H, "Large Lights by qq.d.y");
	g_p_system->set_callback(CSystem::INIT, init);
	g_p_system->set_callback(CSystem::RENDER, render);
	g_p_system->set_callback(CSystem::FRAME, frame);
	g_p_system->run();
	return 0;
}